function unimplemented(message = "not implemented") {
  throw new Error(message);
}

function unreachable() {
  throw new Error(unreachable);
}

function locToStart(loc) {
  return `${loc.file}:${loc.start.line}:${loc.start.column}`
}

class TypeBase {
  constructor(loc) {
    this.loc = loc;
  }
  name() {
    unreachable();
  }
  opeartorPlus() {
    return null;
  }
  opeartorMul() {
    return null;
  }
  opeartorDiv() {
    return null;
  }
}

class TypeCheckError extends TypeBase {
  name () {
    return '[type error]';
  }
}
class TypeAny extends TypeBase {
  name () {
    return 'any';
  }
  opeartorPlus() {
    return TypeAny;
  }
  opeartorMul() {
    return TypeAny;
  }
  opeartorDiv() {
    return TypeAny;
  }
}

class TypeNumber extends TypeBase {
  name() {
    return 'number';
  }
  opeartorPlus(right) {
    if (right instanceof TypeNumber) {
      return TypeNumber;
    }

    return null;
  }

  opeartorMul(right) {
    if (right instanceof TypeNumber) {
      return TypeNumber;
    }

    return null;
  }

  opeartorDiv(right) {
    if (right instanceof TypeNumber) {
      return TypeNumber;
    }

    return null;
  }

}

class TypeRegExp extends TypeBase {
  name() {
    return 'RegExp';
  }

  opeartorMul(right) {
    if (right instanceof TypeRegExp) {
      return TypeRegExp;
    }

    return null;
  }
}

// 原始类型检测
export function typecheck(root) {

  const errors = [];

  const visitor = {
    NumbericLiteral({ loc }) {
      return new TypeNumber(loc);
    },
    RegExpToken({ loc }) {
      return new TypeRegExp(loc);
    },
    BinaryExpression({ left, operatorToken: op, right, loc }) {
      const leftType = visit(left);
      const rightType = visit(right);

      const spanLoc = {
        file: left.loc.file,
        start: left.loc.start,
        end: right.loc.end
      };

      switch (op.type) {
        case "PlusToken": {
          const newType = leftType.opeartorPlus(rightType);
          if (!newType) {
            // throw new Error(`Operator "+" cannot be applied to types "${leftType.name()}" and "${rightType.name()}" at ${locToStart(spanLoc)}.`)
            errors.push(`Operator "+" cannot be applied to types "${leftType.name()}" and "${rightType.name()}" at ${locToStart(spanLoc)}.`);
          }
          return new TypeAny(spanLoc);
          // return new newType(spanLoc);
        }
        case "MulToken": {
          const newType = leftType.opeartorMul(rightType);
          if (!newType) {
            // throw new Error(`Operator "*" cannot be applied to types "${leftType.name()}" and "${rightType.name()}" at ${locToStart(spanLoc)}.`)
            errors.push(`Operator "*" cannot be applied to types "${leftType.name()}" and "${rightType.name()}" at ${locToStart(spanLoc)}.`)
          }
          return new TypeAny(spanLoc);
          // return new newType(spanLoc);
        }
        case "DivToken": {
          const newType = leftType.opeartorDiv(rightType);
          if (!newType) {
            // throw new Error(`Operator "/" cannot be applied to types "${leftType.name()}" and "${rightType.name()}" at ${locToStart(spanLoc)}.`)
            errors.push(`Operator "/" cannot be applied to types "${leftType.name()}" and "${rightType.name()}" at ${locToStart(spanLoc)}.`)
          }
          return new TypeAny(spanLoc);
          // return new newType(spanLoc);
        }
        default:
          throw new Error(`unknow operator "${op.type}" at ${locToStart(op.loc)}`);
      }
    }
  };

  const visit = (node) => {
    if (node == null) throw "something";
    const handler = visitor[node.type];
    if (!handler) {
      throw new Error(`unknow node type "${node.type}" at ${locToStart(node.loc)}`);
    }

    return handler(node);
  }

  visit(root);

  return errors;
}